package ms.jasim.console.gui.component;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.tree.DefaultMutableTreeNode;

import ms.jasim.console.gui.component.MP_EventPanel.SolutionTreeNode;
import ms.jasim.framework.PddlModel;
import ms.jasim.framework.EventSimulation.Solution;
import ms.jasim.framework.PddlModel.Evaluator;
import ms.spm.IAppContext;
import ms.utils.TreeNodeData;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.XYItemLabelGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.general.Dataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class MP_SimulationChart extends JPanel {
	private JComboBox cboFunc;
	private JComboBox cboComponent;
	private JComboBox cboEval;
	private IAppContext context;
	private DefaultComboBoxModel cboEvalModel;
	private DefaultComboBoxModel cboComponentModel;
	private DefaultMutableTreeNode root;
	
	private static final long serialVersionUID = 1L;
	private MyXYItemLabelGenerator chartItemLabel;
	private ChartPanel chartPanel;

	class XYItemLabelItem {
		private int series, item;
		private String label;

		public XYItemLabelItem(int seriesIdx, int i, String string) {
			series = seriesIdx;
			item = i;
			label = string;
		}

		public int getSeries() {
			return series;
		}

		public void setSeries(int series) {
			this.series = series;
		}

		public int getItem() {
			return item;
		}

		public void setItem(int item) {
			this.item = item;
		}

		public String getLabel() {
			return label;
		}

		public void setLabel(String label) {
			this.label = label;
		}
	}

	class MyXYItemLabelGenerator implements XYItemLabelGenerator {

		public final List<XYItemLabelItem> items = new ArrayList<XYItemLabelItem>();

		@Override
		public String generateLabel(XYDataset arg0, int arg1, int arg2) {
			for (XYItemLabelItem item : items)
				if (item.series == arg1 && item.item == arg2)
					return item.label;
			return null;
		}

	}
	
	public MP_SimulationChart() {
		super();
		setLayout(new BorderLayout());

		final JPanel panel = new JPanel();
		final FlowLayout flowLayout = new FlowLayout();
		flowLayout.setAlignment(FlowLayout.LEFT);
		panel.setLayout(flowLayout);
		add(panel, BorderLayout.SOUTH);

		final JLabel viewChartOfLabel = new JLabel();
		viewChartOfLabel.setText("View chart of");
		panel.add(viewChartOfLabel);

		cboEval = new JComboBox();
		cboEval.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				updateSelectedEvaluator();
			}
		});
		cboEval.setModel(cboEvalModel = new DefaultComboBoxModel());

		panel.add(cboEval);

		cboFunc = new JComboBox();
		cboFunc.setModel(new DefaultComboBoxModel(new String[] {"Min", "Max", "Avg"}));
		panel.add(cboFunc);

		cboComponent = new JComboBox();
		cboComponent.setModel(cboComponentModel = new DefaultComboBoxModel());
		panel.add(cboComponent);

		final JButton cmdView = new JButton();
		cmdView.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				createChart();
			}
		});
		cmdView.setText("View");
		panel.add(cmdView);
		chartPanel = new ChartPanel(null);
		this.add(chartPanel, BorderLayout.CENTER);
	}

	protected void updateSelectedEvaluator() {
		Evaluator eval = (Evaluator) cboEval.getSelectedItem();
		if (eval != null) {
			cboComponentModel.removeAllElements();
			for (String comp : eval.getComponents())
				cboComponentModel.addElement(comp);
		}
	}

	public void setContext(IAppContext context) {
		this.context = context;
		PddlModel model = this.context.getService(PddlModel.class);
		if (model != null) {
			for (PddlModel.Evaluator eval : model.Evaluators)
				cboEvalModel.addElement(eval);
		}
	}
	
	public void setSolutionTree(DefaultMutableTreeNode root) {
		this.root = root;
	}

	private float getEvalMetric(List<Float> values) {
		float result = Float.NaN;
		switch (cboFunc.getSelectedIndex()) {
		case 0: // min
			result = Float.MAX_VALUE;
			for (float f : values)
				if (f < result)
					result = f;
			break;
		case 1: // max
			result = Float.MIN_VALUE;
			for (float f : values)
				if (f > result)
					result = f;
			break;
		case 2: // avg
			result = 0;
			for (float f : values)
				result += f;
			result = result / values.size();
		}
		return result;
	}
	
	public void createChart() {
		
		Dataset ds = new XYSeriesCollection();
		
		JFreeChart chart = ChartFactory.createXYLineChart("Solution Simulation", "Simulation Time", cboEvalModel.getSelectedItem().toString() + "/" + cboComponent.getSelectedItem().toString(), (XYDataset) ds, PlotOrientation.VERTICAL, true, true, false);
		
		chartItemLabel = new MyXYItemLabelGenerator();

		XYPlot localXYPlot = (XYPlot) chart.getPlot();
		localXYPlot.setDomainPannable(true);
		localXYPlot.setRangePannable(true);
		XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) localXYPlot.getRenderer();
		renderer.setBaseShapesVisible(true);
		renderer.setBaseShapesFilled(true);
		renderer.setBaseItemLabelsVisible(true);
		renderer.setBaseItemLabelGenerator(chartItemLabel);
		
		populateDataset((String) cboComponentModel.getSelectedItem(), ds, chart);
		
		chartPanel.setChart(chart);		
	}

	private void populateDataset(String component, Dataset dataset, JFreeChart  chart) {
		XYSeriesCollection ds = (XYSeriesCollection)dataset;
		ArrayList<Float> values = new ArrayList<Float>();
		
		String evalName = cboEvalModel.getSelectedItem().toString();
		String evalComponent = cboComponentModel.getSelectedItem().toString();
		
		// remove old series, if any
		ds.removeAllSeries();
		chartItemLabel.items.clear();
		XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) ((XYPlot) chart.getPlot()).getRenderer();
		
		for (int i = 0; i < root.getChildCount(); i++) {
			SolutionTreeNode node = (SolutionTreeNode) root.getChildAt(i);
			Solution sol = (Solution) node.getData().getData();
			
			Enumeration bfe = node.breadthFirstEnumeration();
			
			values.clear();
			
			int currentDepth = node.getDepth();
			DefaultMutableTreeNode lastVisitNode = null;

			XYSeries series = new XYSeries(sol.getName(), true, true);
			int seriesIdx = ds.getSeriesCount();
			ds.addSeries(series);
			renderer.setSeriesStroke(seriesIdx, new BasicStroke(2));

			while (bfe.hasMoreElements()) {
				DefaultMutableTreeNode child = (DefaultMutableTreeNode) bfe.nextElement();
				TreeNodeData cData = (TreeNodeData) child.getUserObject();
				if (cData.getData() instanceof Solution) {
					if (child.getDepth() != currentDepth) {
						if (values.size() > 0 && lastVisitNode != null) {
							TreeNodeData lvnData = (TreeNodeData) lastVisitNode.getUserObject();
							series.add(((Solution) lvnData.getData()).getTime(), getEvalMetric(values));
							if (lastVisitNode.getParent() != root)
								chartItemLabel.items.add(new XYItemLabelItem(seriesIdx, series.getItemCount() - 1, ((DefaultMutableTreeNode) lastVisitNode.getParent()).getUserObject().toString()));
						}
						currentDepth = child.getDepth();
						values.clear();
					}
					
					float cost = ((Solution) cData.getData()).getEvaluatorMetric(evalName).getValue(evalComponent);
					values.add(cost);
					lastVisitNode = child;
				}
			}

			if (values.size() > 0 && lastVisitNode != null) {
				TreeNodeData lvnData = (TreeNodeData) lastVisitNode.getUserObject();
				Solution ls = (Solution) lvnData.getData();
				series.add(ls.getTime(), getEvalMetric(values));
				chartItemLabel.items.add(new XYItemLabelItem(seriesIdx, series.getItemCount() - 1, ((DefaultMutableTreeNode) lastVisitNode.getParent()).getUserObject().toString()));
				if (ls.isEnd()) {
					series.add(ls.getEndTime(), getEvalMetric(values));
				}
			}

		}
	}
}
